﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Text.RegularExpressions;
using EffiProz.Core;

namespace System.Data.EffiProz
{  

    internal class EfzConnectionOptions
    {
        private Dictionary<string, string> _connectionOptions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

        public string ConnectionString
        {
            get;
            set;
        }

        public EfzConnectionOptions()
        {
            SetDefaults();
        }

        public EfzConnectionOptions(string connectionString)
        {
            ParseConnectionString(connectionString);
        }



        public string Database
        {
            get 
            {
                string origDatabase = GetString("initial catalog");

                if (origDatabase == null)
                    return null;

                return ExpandFileName(origDatabase);
            }
        }

        /// <summary>
        /// Expand the filename of the data source, resolving the |DataDirectory| macro as appropriate.
        /// </summary>
        /// <param name="sourceFile">The database filename to expand</param>
        /// <returns>The expanded path and filename of the filename</returns>
        private string ExpandFileName(string sourceFile)
        {
        
            string dataDirectorySymbol = "|DataDirectory|";

            if (String.IsNullOrEmpty(sourceFile)) return sourceFile;

            if (sourceFile.StartsWith(dataDirectorySymbol, StringComparison.OrdinalIgnoreCase))
            {
                string dataDirectory;


                dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory") as string;
                if (String.IsNullOrEmpty(dataDirectory))
                    dataDirectory = AppDomain.CurrentDomain.BaseDirectory;


                if (sourceFile.Length > dataDirectorySymbol.Length)
                {
                    if (sourceFile[dataDirectorySymbol.Length] == System.IO.Path.DirectorySeparatorChar ||
                        sourceFile[dataDirectorySymbol.Length] == System.IO.Path.AltDirectorySeparatorChar)
                        sourceFile = sourceFile.Remove(dataDirectorySymbol.Length, 1);
                }
                sourceFile = System.IO.Path.Combine(dataDirectory, sourceFile.Substring(dataDirectorySymbol.Length));

                sourceFile = System.IO.Path.GetFullPath(sourceFile);
            }

            sourceFile = sourceFile.Trim();

           if(  sourceFile[sourceFile.Length -1] == System.IO.Path.DirectorySeparatorChar ||
               sourceFile[sourceFile.Length -1] == System.IO.Path.AltDirectorySeparatorChar ||
               sourceFile[sourceFile.Length -1] == '\"'
               )
               sourceFile = sourceFile.Substring(0,sourceFile.Length-1);

            if(  sourceFile[0] == '\"')
                sourceFile = sourceFile.Substring(1);

            return sourceFile;
        }


        public int CommandTimeout
        {
            get { return GetInt("command timeout"); }
        }

        public int Port
        {
            get { return GetInt("port"); }
        }


        public string Host
        {
            get { return GetString("host"); }
        }

        public int ConnectionTimeout
        {
            get { return GetInt("connection timeout"); }
        }

        public bool Pooling
        {
            get { return GetBool("pooling"); }
        }

        public bool AutoShutdown
        {
            get 
            {
                if (_connectionOptions.ContainsKey("auto shutdown"))
                {
                    return GetBool("auto shutdown");
                }
                else if (ConnectionType == DatabaseURL.S_FILE)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            
            }
        }

        public bool ReferentialIntergrity
        {
            get { return GetBool("referential intergrity"); }
        }

        public bool IgnoreCase
        {
            get { return GetBool("ignore case"); }
        }

        public int MaxRows
        {
            get { return GetInt("max rows"); }
            set { _connectionOptions["max rows"] = value.ToString(); }
        }

        public int FetchSize
        {
            get { return GetInt("fetch size"); }
            set { _connectionOptions["fetch size"] = value.ToString(); }
        }

        public bool Readonly
        {
            get { return GetBool("readonly"); }
            set { _connectionOptions["readonly"] = value.ToString(); }
        }

        public bool Enlist
        {
            get { return GetBool("enlist"); }
        }

        public bool AutoCommit
        {
            get { return GetBool("auto commit"); }
            set { _connectionOptions["auto commit"] = value.ToString(); }
        }

        public long ConnectionLifeTime
        {
            get { return GetLong("connection lifetime"); ; }
        }

        public int MinPoolSize
        {
            get { return GetInt("min pool size"); }
        }

        public int MaxPoolSize
        {
            get { return GetInt("max pool size"); }
        }

        public string User
        {
            get { return GetString("user"); }
        }

        public string Password
        {
            get { return GetString("password"); }
        }

        public string DataSource
        {
            get { return GetString("data source"); }
        }

        public string InitialCatalog
        {
            get { return GetString("initial catalog"); }
        }

        public string CryptoType
        {
            get { return GetString("crypto type"); }
        }

        public bool Ssl
        {
            get { return GetBool("ssl"); }
        }

        public string CryptoKey
        {
            get { return GetString("crypto key"); }
        }

        public string CryptoIv
        {
            get { return GetString("crypto iv"); }
        }
      
        public string ConnectionType
        {
            get 
            {
                switch (GetString("connection type").ToLowerInvariant())
                {
                    case "memory":
                        return DatabaseURL.S_MEM;
                    case "file":
                        return DatabaseURL.S_FILE;
                    case "server":
                        return DatabaseURL.S_EFZ;
                    case "webserver":
                        return DatabaseURL.S_HTTP;
                    default:
                        return DatabaseURL.S_FILE;
                }
            }
        }

        public IsolationLevel IsolationLevel
        {
            get
            {
                switch (GetString("isolation level").ToLowerInvariant())
                {
                    case "readcommitted":
                        return IsolationLevel.ReadCommitted;

                    case "readuncommitted":
                        return IsolationLevel.ReadUncommitted;

                    case "repeatableread":
                        return IsolationLevel.RepeatableRead;

                    case "serializable":
                        return IsolationLevel.Serializable;
                    default:
                        return IsolationLevel.ReadCommitted;

                }
            }

            set
            {
                switch (value)
                {
                    case IsolationLevel.ReadCommitted:
                        _connectionOptions["isolation level"] = "readcommitted";
                        break;
                    case IsolationLevel.ReadUncommitted:
                        _connectionOptions["isolation level"] = "readuncommitted";
                        break;
                    case IsolationLevel.RepeatableRead:
                        _connectionOptions["isolation level"] = "repeatableread";
                        break;
                    case IsolationLevel.Serializable:
                        _connectionOptions["isolation level"] = "serializable";
                        break;
                    default:
                        _connectionOptions["isolation level"] = "readcommitted";
                        break;
                }
            }
        }


        /// <summary>
        /// Parses the connection string into component parts
        /// </summary>
        /// <param name="connectionString">The connection string to parse</param>
        /// <returns>An array of key-value pairs representing each parameter of the connection string</returns>
        internal void ParseConnectionString(string connectionString)
        {
            SetDefaults();

            ConnectionString = connectionString;
            string s = connectionString;
            int n;
          
            // First split into semi-colon delimited values.  The Split() function of EffiProzDBBase accounts for and properly
            // skips semi-colons in quoted strings
            string[] arParts = s.Split(';');
            string[] arPiece;

            int x = arParts.Length;
            // For each semi-colon piece, split into key and value pairs by the presence of the = sign
            for (n = 0; n < x; n++)
            {
                arPiece = arParts[n].Split('=');
                if (arPiece.Length == 2)
                {
                    _connectionOptions[arPiece[0].Trim()] = arPiece[1].Trim();
                }
                else if (arPiece.Length == 1)
                {
                    _connectionOptions[arPiece[0].Trim()] = "";
                }
                else throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, "Invalid ConnectionString format for parameter \"{0}\"", (arPiece.Length > 0) ? arPiece[0] : "null"));
            }

            if (!String.IsNullOrEmpty(DataSource))
            {
                string[] parts = DataSource.Split(':');
                _connectionOptions["host"]  = parts[0];
                if (parts.Length > 1)
                {
                    _connectionOptions["port"] = parts[1];
                }
            }
            else if (!String.IsNullOrEmpty(Host))
            {
                if(Port != 0)
                    _connectionOptions["data source"]= String.Format("{0}:{1}",Host,Port);
                else
                    _connectionOptions["data source"] = String.Format("{0}",Host);
            }

        }

        private string GetString(string key)
        {
            if (_connectionOptions.ContainsKey(key))
            {
                return (string)_connectionOptions[key];
            }

            return null;
        }

        private bool GetBool(string key)
        {
            if (_connectionOptions.ContainsKey(key))
            {
                return Boolean.Parse(_connectionOptions[key]);
            }

            return false;
        }

      
        private int GetInt(string key)
        {
            if (_connectionOptions.ContainsKey(key))
            {
                return Convert.ToInt32(_connectionOptions[key], CultureInfo.InvariantCulture);
            }

            return 0;
        }

        private long GetLong(string key)
        {
            if (_connectionOptions.ContainsKey(key))
            {
                return Convert.ToInt64(_connectionOptions[key], CultureInfo.InvariantCulture);
            }

            return 0;
        }

        private void SetDefaults()
        {
           
            _connectionOptions.Clear();

            _connectionOptions.Add("connection type", DatabaseURL.S_FILE);
            _connectionOptions.Add("data source", string.Empty);           
            _connectionOptions.Add("user", string.Empty);
            _connectionOptions.Add("password", string.Empty);
            _connectionOptions.Add("initial catalog", string.Empty);
            _connectionOptions.Add("pooling", "false");           
            _connectionOptions.Add("min pool size", "0");
            _connectionOptions.Add("max pool size", "50");
            _connectionOptions.Add("connection lifetime", "600");
            _connectionOptions.Add("command timeout", "30");
            _connectionOptions.Add("server type", "file");
            _connectionOptions.Add("isolation level", IsolationLevel.ReadCommitted.ToString());
            _connectionOptions.Add("max rows", "0");
            _connectionOptions.Add("fetch size", "0");
            _connectionOptions.Add("host", "localhost");
            _connectionOptions.Add("port", "9001");
            _connectionOptions.Add("readonly", "false");
            _connectionOptions.Add("ssl", "false");
        }

        public void Validate()
        {
            if (GetString("isolation level") != null)
            {
                switch (GetString("isolation level").ToLowerInvariant())
                {
                    case "readcommitted":
                    case "readuncommitted":
                    case "repeatableread":
                    case "serializable":
                        break;
                    default:
                        throw new ArgumentException("Specified Isolation Level is not valid.");
                }
            }

            if(GetString("connection type") == null)
                throw new ArgumentException("Required attribute connection type is missing.");
            switch (GetString("connection type").ToLowerInvariant())
            {
                case "memory":
                case "file":
                case "server":
                case "webserver":
                    break;
                default:
                    throw new ArgumentException("Specified connection type is not valid.");
            }

            if (CryptoType != null)
            {
                switch (CryptoType.ToLowerInvariant())
                {
                    case "des":
                    case "tripledes":
                    case "aes":
                    case "rijndael":
                        break;
                    default:
                        throw new ArgumentException("Specified crypto type is not valid.");
                }
            }


            if ((String.IsNullOrEmpty(ConnectionType)))
                throw new ArgumentException("Required connection string arguments missing: connection type");

            if( (String.IsNullOrEmpty(Database)) )
                throw new ArgumentException("Required connection string arguments missing: initial catalog");

            if(String.IsNullOrEmpty(DataSource) &&  (ConnectionType == DatabaseURL.S_HTTP 
                || ConnectionType == DatabaseURL.S_EFZ))
                throw new ArgumentException("Required connection string arguments missing: data source");

            if(MinPoolSize > MaxPoolSize)
                throw new ArgumentException("MinPoolSize should be less than MaxPoolSize");

            if (!String.IsNullOrEmpty(CryptoType) && (String.IsNullOrEmpty(CryptoKey) ||
                String.IsNullOrEmpty(CryptoIv)))
                throw new ArgumentException("Required connection string arguments missing: 'crypto key' ,'crypto iv'");                   
        }
    }
}
